﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using Caliburn.Micro;
using CardPlay.Model.CardModel;
using CardPlay.Model.GameModel;
using CardPlay.Model.GameModel.Actions;
using CardPlay.Model.GameModel.Presentation;
using CardPlay.Model.Layout;
using CardPlay.Model.Primitives;
using CardPlay.Presentation;
using Point = CardPlay.Model.Primitives.Point;
using Size = CardPlay.Model.Primitives.Size;

namespace CardPlay.Client.ViewModels
{
    public partial class GameCanvas : PropertyChangedBase, ICanvas
    {
        private readonly IGraphics _graphics;
        private readonly LayoutMetrics _metrics;
        private readonly GridLayout _layout;

        private readonly ObservableCollection<PileViewModel> _elements = new ObservableCollection<PileViewModel>();

        private Size _canvasSize = Size.Empty;
        private Size _viewportSize = Size.Empty;

        public GameCanvas(IGraphics graphics)
        {
            _graphics = graphics;

            _metrics = new LayoutMetrics
                       {
                           CardSize = new Size((int) _graphics.CardSize.Width, (int) _graphics.CardSize.Height),
                           MaxSpacingAcross = 40,
                           MinSpacingAcross = 10,
                           MaxSpacingDown = 20,
                           MinSpacingDown = 10,
                           StackingOffsetOnFaceDownCard = new Size(4, 6),
                           StackingOffsetOnFaceUpCard = new Size(25, 28)
                       };

            _layout = new GridLayout { Columns = 1, Rows = 1, Margin = new MarginSize(20) };
        }

        public IEnumerable<Rectangle> LayoutRegions
        {
            get { return RegionsOf(_layout); }
        }

        private static IEnumerable<Rectangle> RegionsOf(ILayoutSite layout)
        {
            if (layout == null) yield break;

            yield return layout.Bounds;

            var children = layout as IEnumerable;
            if (children == null) yield break;

            foreach (var item in children.OfType<ILayoutSite>().SelectMany(RegionsOf))
            {
                yield return item;
            }
        }


        public PartLocation HitTest(int x, int y)
        {
            return HitTest(new Rectangle(x, y, 1, 1));
        }

        public PartLocation HitTest(Rectangle bounds)
        {
            return _elements.Select(pile => pile.HitTest(bounds)).FirstOrDefault(location => !location.IsUndefined);
        }

        public IEnumerable<PartLocation> PilesUnder(Rectangle bounds)
        {
            return _elements.Select(pile => pile.HitTest(bounds)).Where(location => !location.IsUndefined);
        }

        public Size ViewportSize
        {
            get { return _viewportSize; }
            set
            {
                if (_viewportSize == value) return;

                _canvasSize = Size.Empty;
                _viewportSize = value;
                UpdateLayout();

                NotifyOfPropertyChange(() => ViewportSize);
            }
        }

        public Size Size
        {
            get { return _canvasSize; }
            set
            {
                if (_canvasSize == value) return;

                _canvasSize = value;
                UpdateLayout();

                NotifyOfPropertyChange(() => Size);
            }
        }

        object ICanvas.ImageForEmptyCue(EmptyPileCue emptyCue)
        {
            return _graphics.CreateEmptyCue(emptyCue);
        }

        object ICanvas.ImageForCard(Card card)
        {
            return _graphics.CreateImageForCard(card);
        }

        Rectangle ICanvas.TightestBounds
        {
            get
            {
                var result = Piles.Select(pile => pile.TightestBounds).Aggregate(Rectangle.Empty, Rectangle.Union);

                result.Inflate(4, 4);
                return result;
            }
        }

        public LayoutMetrics Metrics
        {
            get { return _metrics; }
        }

        public ILayoutSite Layout
        {
            get { return _layout.Children.FirstOrDefault();  }
            set
            {
                _layout.Children.Clear();
                _canvasSize = Size.Empty;

                if (value != null) _layout.Add(value);

                UpdateLayout();
                UpdateCues();
            }
        }

        public ObservableCollection<PileViewModel> Piles
        {
            get { return _elements; }
        }

        public IEnumerable<CardViewModel> Cards
        {
            get { return _elements.SelectMany(pile => pile.Cards); }
        }

        public IDraggable CreateDragPile(IPlayingContext playingContext, RemovePileAction action)
        {
            return _elements
                .Where(item => item.Pile == action.SourcePile)
                .Select(sourcePile => new MoveController(this, playingContext, action, sourcePile))
                .FirstOrDefault();
        }

        public IDisposable CreatePeekCard(IPlayingContext playingContext, PeekCardAction action)
        {
            return _elements
                .Where(item => item.Pile.Location.Path == action.Location.Path)
                .Select(sourcePile => new PeekController(this, action, sourcePile))
                .FirstOrDefault();
        }

        public void Add(PileViewModel pile)
        {
            _elements.Add(pile);
        }

        public void Remove(PileViewModel dragPile)
        {
            _elements.Remove(dragPile);
        }

        public void Clear()
        {
            foreach (var pile in _elements)
                pile.ParentNode = null;

            _elements.Clear();
        }

        private bool _inUpdateLayout;

        public void UpdateLayout()
        {
#if !CONSIDER_SCROLLBARS
            if (_inUpdateLayout) return;
            {
                _inUpdateLayout = true;

#if SILVERLIGHT
                var cxScroll = 18;
                var cyScroll = 18;
#else
                var cxScroll = (int) SystemParameters.ScrollWidth;
                var cyScroll = (int) SystemParameters.ScrollHeight;
#endif

                Size[] sizeReductions =
                {
                    Size.Empty,                     // without scroll bars
                    //new Size(cxScroll, 0),          // with vertical scroll bar
                    //new Size(cxScroll, cyScroll),   // with both scroll bars
                };

                var previousSize = new Size(-1, -1);

                foreach (var targetSize in sizeReductions.Select(scrollerSize => ViewportSize - scrollerSize))
                {
                    if (targetSize.Width >= 0 && targetSize.Height >= 0 && targetSize != previousSize)
                    {
                        _layout.Measure(null, targetSize);

                        if (_layout.MeasuredSize.Width <= targetSize.Width && _layout.MeasuredSize.Height <= targetSize.Height)
                            break;
                    }

                    previousSize = targetSize;
                }

                _layout.Arrange(null, new Rectangle(Point.Empty, _layout.MeasuredSize));
                Size = _layout.MeasuredSize.BeyondLowerLimit(Size).BeyondLowerLimit(new Size(800, 600));

                _inUpdateLayout = false;
            }

#else
            if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0) return;

            _layout.Measure(null, _canvasSize);

            var layoutSize = _layout.MeasuredSize.DesiredSize;

            var bounds = layoutSize.PositionWithin(new Rectangle(Point.Empty, _canvasSize), HorizontalAlignment.Center, VerticalAlignment.Stretch);
            _layout.Arrange(null, bounds);
#endif
            NotifyOfPropertyChange(() => LayoutRegions);
        }

        public void UpdateCues()
        {
            foreach (var pile in _elements)
                pile.UpdateCues();
        }
    }
}